package org.sfnelson.sk.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.sfnelson.sk.client.data.User;
import org.sfnelson.sk.client.event.Created;
import org.sfnelson.sk.client.event.Joined;
import org.sfnelson.sk.client.event.Left;
import org.sfnelson.sk.client.event.ReceivedLoot;
import org.sfnelson.sk.client.rpc.EventListener;
import org.sfnelson.sk.client.rpc.EventManager;

import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;

public class LadderPanel extends Composite implements EventListener {

	private final Map<User, LadderEntry> entries = new HashMap<User, LadderEntry>();

	private final ArrayList<User> ladder = new ArrayList<User>();

	private final Panel container;
	private final AddUser add;
	private final RemoveUser remove;

	public LadderPanel(Panel header, Panel footer, AddUser add, RemoveUser remove) {
		container = new FlowPanel();
		container.setStyleName("contents");
		this.add = add;
		this.remove = remove;

		Panel wrapper = new FlowPanel();
		wrapper.add(container);
		wrapper.add(header);
		wrapper.add(footer);
		wrapper.setStyleName("container");

		Panel widget = new FlowPanel();
		widget.add(wrapper);
		initWidget(widget);

		setStyleName("ladder");

		EventManager.impl().addListener(this);
	}

	public void onReceivedLoot(ReceivedLoot event) {
		int position = 0;
		while (!ladder.get(position).equals(event.user)) {
			if (++position >= ladder.size()) return;
		}

		while (true) {
			int next = position;
			do {
				if (++next >= ladder.size()) return;
			} while (!entries.get(ladder.get(next)).active());

			swap(position, next);

			position = next;
		}
	}

	private void swap(int a, int b) {
		Element ea = entries.get(ladder.get(a)).getElement();
		Element eb = entries.get(ladder.get(b)).getElement();
		Element parent = ea.getParentElement();
		if (a == b) return;
		if (a == 0) {
			parent.insertBefore(ea, eb);
			parent.insertBefore(eb, parent.getFirstChild());
		} else {
			Node mark = ea.getPreviousSibling();
			parent.insertBefore(ea, eb);
			parent.insertBefore(eb, mark.getNextSibling());
		}

		User ua = ladder.get(a);
		User ub = ladder.get(b);
		ladder.set(a, ub);
		ladder.set(b, ua);
	}

	public void onCreated(Created event) {
		LadderEntry entry = entries.get(event.user);

		if (entry == null) {
			entry = new LadderEntry(event.user);
			entries.put(event.user, entry);

			int end = ladder.size();
			int position = event.user.random(0, end);

			container.add(entry);
			if (position != end) {
				Element child = entry.getElement();
				Element parent = child.getParentElement();
				Element current = entries.get(ladder.get(position)).getElement();
				parent.insertBefore(child, current);
			}
			ladder.add(position, event.user);
		}
	}

	public void onJoined(Joined event) {
		LadderEntry entry = entries.get(event.user);

		if (entry != null) {
			entry.present();
		} else {
			throw new RuntimeException("User is not present: " + event.user);
		}

	}

	public void onLeft(Left event) {
		LadderEntry entry = entries.get(event.user);

		if (entry != null) {
			entry.absent();
		} else {
			throw new RuntimeException("User is not present: " + event.user);
		}
	}

	private class LadderEntry extends Composite {

		private boolean active = false;

		public LadderEntry(final User user) {
			Panel container = new FlowPanel();
			container.add(new Label(user.getName()));

			Button add = new Button("Add");
			add.setStyleName("add");
			add.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {
					LadderPanel.this.add.addUser(user);
				}
			});
			container.add(add);
			Button remove = new Button("Remove");
			remove.setStyleName("remove");
			remove.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {
					LadderPanel.this.remove.removeUser(user);
				}
			});
			container.add(remove);

			initWidget(container);
			setStyleName("entry");
		}

		public boolean active() {
			return active;
		}

		public void present() {
			active = true;
			addStyleName("active");
		}

		public void absent() {
			active = false;
			removeStyleName("active");
		}
	}
}
